package com.slipper.ai.flow;

import com.slipper.ai.flow.vo.*;
import com.slipper.constants.Constants;
import com.slipper.exception.CommonException;
import org.springframework.stereotype.Component;

import java.util.*;

@Component
public class FlowEngine {
    public ResultVO start(FlowVO flow) {
        if (hasCycle(flow)) throw new RuntimeException("流程存在回环，请先修复！");

        NodeVO startNode = findFirst(flow.getNodes());
        if (startNode == null) throw new RuntimeException("未配置开始节点！");

        long startTime = System.currentTimeMillis();
        ResultVO result = new ResultVO();
        result.setInput(flow.getFlowData());

        Queue<NodeVO> nodeQueue = new LinkedList<>();
        addNode(nodeQueue, startNode);

        while (!nodeQueue.isEmpty()) {
            NodeVO curNode = nodeQueue.poll();

            NodeInstVO nodeInst = result.getNodeMap().get(curNode.getId());
            if (nodeInst == null) {
                nodeInst = new NodeInstVO();
                nodeInst.setSessionId(flow.getSessionId());
                nodeInst.setNodeId(curNode.getId());
                nodeInst.setNodeName(curNode.getName());
                result.getNodeMap().put(curNode.getId(), nodeInst);
            }

            if (hasPreNode(nodeInst, flow, result, 0)) {
                addNode(nodeQueue, curNode);
                continue;
            }

            if (!result.getNodeList().contains(nodeInst)) {
                result.getNodeList().add(nodeInst);
            }

            nodeInst.getInput().putAll(parseData(curNode, result));
            nodeInst.setInputValue(Parser.parseVariable(curNode.getSourceParamId(), result));

            long nodeStartTime = System.currentTimeMillis();
            try {
                List<LineVO> outgoingLines = NodeDealerFactory
                        .getNodeDealer(curNode.getType())
                        .doDeal(flow, curNode, nodeInst);

                outgoingLines.forEach(line -> flow.getNodes()
                        .stream()
                        .filter(node -> node.getId().equals(line.getTarget()))
                        .findFirst()
                        .ifPresent(item -> {
                            addNode(nodeQueue, item);
                            result.getLineList().add(line);
                        }));
                nodeInst.setCode(Constants.YES);
            } catch (Exception e) {
                nodeInst.setCode(Constants.NO);
                nodeInst.setMessage(e.getMessage());
                result.addMessage(e.getMessage());
            }
            nodeInst.setTime(System.currentTimeMillis() - nodeStartTime);
        }

        result.getNodeMap().forEach((key, value) -> {
            if (result.getLineList().stream().noneMatch(line -> line.getSource().equals(key))) {
                result.getOutput().putAll(value.getOutput());
                result.addContent(value.getContent());
            }
        });
        result.setTime(System.currentTimeMillis() - startTime);

        return result;
    }

    private void addNode(Queue<NodeVO> queue, NodeVO node) {
        if (queue.contains(node)) return;
        queue.add(node);
    }

    private boolean hasPreNode(NodeInstVO nodeInst, FlowVO flow, ResultVO result, int checkTimes) {
        if (checkTimes > 1000) throw new CommonException("前置节点核对次数超限，可能存在回环，请核对流程。");

        List<LineVO> inLineList = flow.getLines()
                .stream()
                .filter(line -> line.getTarget().equals(nodeInst.getNodeId()))
                .toList();
        if (inLineList.isEmpty()) {
            nodeInst.setFinished(true);
            return false;
        }

        boolean hasPreNode = false;
        for (LineVO line : inLineList) {
            NodeInstVO preNodeInst = result.getNodeMap().get(line.getSource());
            if (preNodeInst == null
                    || (!preNodeInst.isFinished()
                        && hasPreNode(preNodeInst, flow, result, checkTimes + 1))) {
                hasPreNode = true;
                break;
            }
        }

        return hasPreNode;
    }

    public boolean hasCycle(FlowVO flow) {
        List<NodeVO> nodes = flow.getNodes();
        List<LineVO> lines = flow.getLines();
        if (nodes == null || nodes.isEmpty()) return false;

        Map<String, Integer> inDegree = new HashMap<>();
        Map<String, List<String>> outLine = new HashMap<>();

        for (NodeVO node : nodes) {
            inDegree.put(node.getId(), 0);
            outLine.put(node.getId(), new ArrayList<>());
        }

        for (LineVO line : lines) {
            String source = line.getSource();
            String target = line.getTarget();

            if (inDegree.containsKey(source) && inDegree.containsKey(target)) {
                outLine.get(source).add(target);
                inDegree.merge(target, 1, Integer::sum);
            }
        }

        Queue<String> queue = new ArrayDeque<>();
        inDegree.forEach((id, degree) -> { if (degree == 0) queue.add(id); });

        int processed = 0;
        while (!queue.isEmpty()) {
            String cur = queue.poll();
            processed++;
            for (String next : outLine.get(cur)) {
                int newDegree = inDegree.merge(next, -1, Integer::sum);
                if (newDegree == 0) queue.add(next);
            }
        }

        return processed != nodes.size();
    }

    public NodeVO findFirst(List<NodeVO> nodes) {
        return nodes.stream()
                .filter(item -> NodeVO.TYPE_START.equals(item.getType()))
                .findFirst()
                .orElse(null);
    }

    public Map<String, Object> parseData(NodeVO node, ResultVO result) {
        if (NodeVO.TYPE_START.equals(node.getType())) {
            return result.getInput();
        }

        Map<String, Object> resultMap = new LinkedHashMap<>();
        if (node.getInputList() == null || node.getInputList().isEmpty()) {
            return resultMap;
        }

        node.getInputList().forEach(item -> {
            if (item.getManual() != null && item.getManual()) {
                resultMap.put(item.getName(), item.getValue());
            } else {
                resultMap.put(item.getName(), Parser.parseVariable(item.getSourceParamId(), result));
            }
        });

        return resultMap;
    }
}
